#ifndef SIMPLE_SUPPORT_ALGORITHM_PICK_UNIQUE_HPP
#define SIMPLE_SUPPORT_ALGORITHM_PICK_UNIQUE_HPP

#include <functional> // std::equal_to, std::not_fn
#include <type_traits> // std::is_invocable_v
#include "../function_utils.hpp" // support::invoke
#include "../range.hpp" // support::range
#include "../algorithm/traits.hpp" // support::is_range_v
#include "range_wrappers.hpp" // support::find

namespace simple::support
{

	// like unique_copy except pick which one you want, cause it's an equivalence not equality
	// forward - you can get the second one if it exists
	// bidirectional - you can get the last one, or second to last
	// random access - the world is your oyster, pick the middle one or something
	// but you are not really meant to read values, cause that smells like a different algorithm
	//
	// TODO: for forward and bidirectional might be useful to keep track of the
	// size, that's something find has got to return to us though, and it all
	// needs be totally opptional and no cost... nah guess it's an iterator
	// thing, a counting iterator that counts how many times it was
	// incremented, kind of related to offset iterator eh
	template <typename ForwardIt, typename OutIt, typename Predicate, typename Pick>
	constexpr OutIt pick_unique(ForwardIt begin, ForwardIt end, Predicate&& predicate, Pick&& pick, OutIt out)
	{
		while(begin != end)
		{
			auto next = find(range{begin, end}, *begin,
				std::not_fn(std::forward<Predicate>(predicate)));
			// TODO use SFINAE for better error message that will list candidates
			// otherwise range version is hidden
			if constexpr (std::is_invocable_v<Pick, range<ForwardIt>>)
				*out = *support::invoke(pick, range{begin, next});
			else
				*out = *support::invoke(pick, begin, next);
			++out;
			begin = next;
		}

		return out;
	}

	template <typename ForwardIt, typename OutIt, typename Pick>
	constexpr OutIt pick_unique(ForwardIt begin, ForwardIt end, Pick&& pick, OutIt out)
	{ return pick_unique(begin, end, std::equal_to<>{}, std::forward<Pick>(pick), out); }

	template <typename Range, typename OutIt, typename Predicate, typename Pick,
		std::enable_if_t<is_range_v<Range>>* = nullptr>
	constexpr OutIt pick_unique(const Range& range, Predicate&& predicate, Pick&& pick, OutIt out)
	{
		using std::begin; using std::end;
		return pick_unique(begin(range), end(range), std::forward<Predicate>(predicate),
			std::forward<Pick>(pick), out);
	}

	template <typename Range, typename OutIt, typename Pick,
		std::enable_if_t<is_range_v<Range>>* = nullptr>
	constexpr OutIt pick_unique(const Range& range, Pick&& pick, OutIt out)
	{
		using std::begin; using std::end;
		return pick_unique(begin(range), end(range), std::forward<Pick>(pick), out);
	}

} // namespace simple::support

#endif /* end of include guard */
